#pragma once
#include "../3rd/cpp-httplib/httplib.h"
#include "../3rd/rapidjson/include/rapidjson/document.h"
#include "utils/dbutils.hpp"
#include "utils/kvutils.hpp"
#include "utils/timeutils.hpp"
#include "utils/transutils.hpp"

using namespace httplib;
using namespace rapidjson;

class DistributionFilter {
 public:
  static DistributionFilter *GetInstance() {
    static DistributionFilter filter{"", ""};
    return &filter;
  }
  std::string doneeId;
  std::string suppliesName;
};

class Distribution_ {
 public:
  std::string distributionId;  // 物资 ID
  std::string doneeId;         // 捐赠者 ID
  std::string suppliesName;    // 物资名称
  std::string type;            // 物资种类
  std::string specifications;  // 规格
  std::string units;           // 单位
  int number;                  // 数量
  std::string distributeTime;  // 捐赠时间

  template <typename Writer>
  void Serialize(Writer &writer) const {
    writer.StartObject();
    writer.String("distributionId");
    writer.String(distributionId.c_str());
    writer.String("doneeId");
    writer.String(doneeId.c_str());
    writer.String("suppliesName");
    writer.String(suppliesName.c_str());
    writer.String("type");
    writer.String(type.c_str());
    writer.String("specifications");
    writer.String(specifications.c_str());
    writer.String("units");
    writer.String(units.c_str());
    writer.String("number");
    // writer.Int(number);
    writer.String(std::to_string(number).c_str());
    writer.String("distributeTime");
    writer.String(distributeTime.c_str());
    writer.EndObject();
  }
};

void Distribution(const Request &req, Response &res) {
  res.set_header("Access-Control-Allow-Origin", "*");

  Document doc;
  doc.Parse(req.body.c_str());
  auto currentPage = std::stoi(req.get_param_value("currentPage"));
  auto pageSize = std::stoi(req.get_param_value("pageSize"));
  auto offset = (currentPage - 1) * pageSize;

  auto sql = "select * from distributions where suppliesName like " +
             FuzzyString(DistributionFilter::GetInstance()->suppliesName);

  if (!DistributionFilter::GetInstance()->doneeId.empty()) {
    sql +=
        " and doneeId = " + Qstring(DistributionFilter::GetInstance()->doneeId);
  }
  sql += " limit " + std::to_string(offset) + ", " + std::to_string(pageSize) +
         ";";

  auto result = DBWrapper::GetInstance()->Select(sql);

  std::string json;
  json += "[";
  bool first = true;
  for (size_t i = 0; i < result->size(); ++i) {
    const auto &row = result->at(i);

    Distribution_ distribution{
        row[0], row[1], row[2], row[3], row[4], row[5], std::stoi(row[6]),
        row[7]};
    if (!first) {
      json += ", ";
    }
    json += Object2Json(distribution);
    first = false;
  }
  json += "]";

  res.status = 200;
  res.set_content(json, "application/json");
}

void DistributionTotal(const Request &req, Response &res) {
  res.set_header("Access-Control-Allow-Origin", "*");

  DistributionFilter::GetInstance()->doneeId = "";
  DistributionFilter::GetInstance()->suppliesName = "";

  auto sql = "select count(*) from distributions;";
  auto result = DBWrapper::GetInstance()->Select(sql);
  hassert(result.get()->size() > 0, "SELECT no rows");
  res.status = 200;
  res.set_content("{\"totalRow\": " + result->begin()->at(0) + "}",
                  "application/json");
}

void DistributionAdd(const Request &req, Response &res) {
  res.set_header("Access-Control-Allow-Origin", "*");

  Document doc;
  doc.Parse(req.body.c_str());
  auto distributionId = Qstring("NULL");                         // 物资 ID
  auto doneeId = Qstring(doc["doneeId"].GetString());            // 捐赠者 ID
  auto suppliesName = Qstring(doc["suppliesName"].GetString());  // 物资名称
  auto type = Qstring(doc["type"].GetString());                  // 物资种类
  auto specifications = Qstring(doc["specifications"].GetString());  // 规格
  auto units = Qstring(doc["units"].GetString());                    // 单位
  auto number = Qstring(std::stoi(doc["number"].GetString()));       // 数量
  auto distributeTime = Qstring(GetDate());

  if (DBWrapper::GetInstance()->Insert(
          "distributions", {distributionId, doneeId, suppliesName, type,
                            specifications, units, number, distributeTime})) {
    auto kv_key = Concatenate({RawString(suppliesName), RawString(type),
                               RawString(specifications), RawString(units)});
    KVWrapper::GetInstance("warehouse")->Add(kv_key, -std::stoi(number));
    res.status = 200;
  } else {
    res.status = 202;
  }
}

void DistributionDelete(const Request &req, Response &res) {
  res.set_header("Access-Control-Allow-Origin", "*");

  auto distributionId = req.get_param_value("distributionId");

  auto sql =
      "delete from distributions where distributionId=" + distributionId + ";";
  auto sel_sql =
      "select * from distributions where distributionId=" + distributionId +
      ";";
  auto result = DBWrapper::GetInstance()->Select(sel_sql);
  auto row = *result->begin();
  auto kv_key = Concatenate({row[2], row[3], row[4], row[5]});
  KVWrapper::GetInstance("warehouse")->Add(kv_key, std::stoi(row[6]));

  if (DBWrapper::GetInstance()->Excute(sql)) {
    res.status = 200;
  } else {
    res.status = 202;
  }
}

void DistributionEdit(const Request &req, Response &res) {
  res.set_header("Access-Control-Allow-Origin", "*");

  Document doc;
  doc.Parse(req.body.c_str());
  auto distributionId = Qstring(doc["distributionId"].GetString());  // 物资 ID
  auto doneeId = Qstring(doc["doneeId"].GetString());            // 捐赠者 ID
  auto suppliesName = Qstring(doc["suppliesName"].GetString());  // 物资名称
  auto type = Qstring(doc["type"].GetString());                  // 物资种类
  auto specifications = Qstring(doc["specifications"].GetString());  // 规格
  auto units = Qstring(doc["units"].GetString());                    // 单位
  auto number = Qstring(std::stoi(doc["number"].GetString()));       // 数量
  auto distributeTime = Qstring(doc["distributeTime"].GetString());

  auto sql =
      "delete from distributions where distributionId=" + distributionId + ";";

  auto sel_sql =
      "select * from distributions where distributionId=" + distributionId +
      ";";
  auto result = DBWrapper::GetInstance()->Select(sel_sql);
  auto row = *result->begin();
  auto kv_key = Concatenate({row[2], row[3], row[4], row[5]});
  KVWrapper::GetInstance("warehouse")
      ->Add(kv_key, std::stoi(row[6]) - std::stoi(number));

  if (DBWrapper::GetInstance()->Excute(sql) &&
      DBWrapper::GetInstance()->Insert(
          "distributions", {distributionId, doneeId, suppliesName, type,
                            specifications, units, number, distributeTime})) {

    res.status = 200;
  } else {
    res.status = 202;
  }
}

void DistributionSearch(const Request &req, Response &res) {
  res.set_header("Access-Control-Allow-Origin", "*");
  DistributionFilter::GetInstance()->doneeId = req.get_param_value("doneeId");
  DistributionFilter::GetInstance()->suppliesName =
      req.get_param_value("suppliesName");

  auto sql = "select count(*) from distributions where suppliesName like " +
             FuzzyString(DistributionFilter::GetInstance()->suppliesName);

  if (!DistributionFilter::GetInstance()->doneeId.empty()) {
    sql +=
        " and doneeId = " + Qstring(DistributionFilter::GetInstance()->doneeId);
  }
  sql += ";";
  auto result = DBWrapper::GetInstance()->Select(sql);
  res.set_content("{\"totalRow\": " + result->at(0).at(0) + "}",
                  "application/json");
  res.status = 200;
}

void DistributionDonee(const Request &req, Response &res) {
  res.set_header("Access-Control-Allow-Origin", "*");

  auto sql = "select * from Distributions where doneeId = " +
             Qstring(req.get_param_value("doneeId")) + ";";

  auto result = DBWrapper::GetInstance()->Select(sql);

  std::string json;
  json += "[";
  bool first = true;
  for (size_t i = 0; i < result->size(); ++i) {
    const auto &row = result->at(i);
    Distribution_ distrbution{
        row[0], row[1], row[2], row[3], row[4], row[5], std::stoi(row[6]),
        row[7]};
    if (!first) {
      json += ", ";
    }
    json += Object2Json(distrbution);
    first = false;
  }
  json += "]";

  res.status = 200;
  res.set_content(json, "application/json");
}